package com.google.code.dnaep.jdo;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;

import javax.jdo.JDOHelper;
import javax.jdo.identity.SingleFieldIdentity;
import javax.jdo.listener.DirtyLifecycleListener;
import javax.jdo.listener.InstanceLifecycleEvent;
import javax.jdo.listener.LoadLifecycleListener;
import javax.jdo.listener.StoreLifecycleListener;

import com.google.code.dnaep.jdo.annotations.PolymorphicRelationship;

public class PolymorphicRelationshipLifecycleListener implements LoadLifecycleListener, StoreLifecycleListener, DirtyLifecycleListener {
	private static final Logger log = Logger.getLogger(PolymorphicRelationshipLifecycleListener.class.getName());

	@Override
	public void postStore(InstanceLifecycleEvent event) {
		// TODO Auto-generated method stub
	}

	@Override
	public void preStore(InstanceLifecycleEvent event) {
		Object pc = event.getPersistentInstance();

		javax.jdo.PersistenceManager pm = getPersistenceManager(pc);

		Field[] polymorphicRelationshipFields = getPolymorphicRelationshipFields(pc);

		// Persist PolymorphicRelationship objects
		for (Field field : polymorphicRelationshipFields) {
			try {
				Object fieldValue = field.get(pc);
				if (fieldValue != null) {
					if (fieldValue instanceof Collection) {
						//TODO reuse Collection instance
						fieldValue = pm.makePersistentAll((Collection<?>) fieldValue);
					} else {
						fieldValue = pm.makePersistent(fieldValue);
					}
					field.set(pc, fieldValue);
				}
			} catch (IllegalArgumentException e) {
				throw new RuntimeException(e);
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			}
		}

		// Set PolymorphicRelationship keys
		for (Field field : polymorphicRelationshipFields) {
			Field keyField = getPolymorphicRelationshipKeyField(field);

			try {
				Object fieldValue = field.get(pc);
				Object keyFieldValue;
				if (fieldValue == null) {
					keyFieldValue = null;
				} else if (fieldValue instanceof Collection) {
					Collection<Object> fieldValue_Collection = (Collection<Object>) fieldValue;
					//TODO reuse Collection instance
					Collection<Object> keyFieldValue_Collection = new ArrayList<Object>();
					for (Object fieldValue_instance : fieldValue_Collection) {
						Object keyFieldValue_instance = pm.getObjectId(fieldValue_instance);
						if (keyFieldValue_instance == null) {
							throw new NullPointerException();
						} else if (keyFieldValue_instance instanceof SingleFieldIdentity) {
							keyFieldValue_instance = ((SingleFieldIdentity) keyFieldValue_instance).getKeyAsObject();
						}
						keyFieldValue_Collection.add(keyFieldValue_instance);
					}
					keyFieldValue = keyFieldValue_Collection;
				} else {
					keyFieldValue = pm.getObjectId(fieldValue);
					if (keyFieldValue == null) {
						throw new NullPointerException();
					} else if (keyFieldValue instanceof SingleFieldIdentity) {
						keyFieldValue = ((SingleFieldIdentity) keyFieldValue).getKeyAsObject();
					}
				}
				keyField.set(pc, keyFieldValue);
			} catch (IllegalArgumentException e) {
				throw new RuntimeException(e);
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public void preDirty(InstanceLifecycleEvent event) {
		// TODO Auto-generated method stub

	}

	@Override
	public void postDirty(InstanceLifecycleEvent event) {
		// TODO Auto-generated method stub

	}

	@Override
	public void postLoad(InstanceLifecycleEvent event) {
		Object pc = event.getPersistentInstance();

		javax.jdo.PersistenceManager pm = getPersistenceManager(pc);

		Field[] polymorphicRelationshipFields = getPolymorphicRelationshipFields(pc);
		
		// Load PolymorphicRelationship objects
		for (Field field : polymorphicRelationshipFields) {
			Field keyField = getPolymorphicRelationshipKeyField(field);

			try {
				Object keyFieldValue = keyField.get(pc);
				Object fieldValue;
				if (keyFieldValue == null) {
					fieldValue = null;
				} else if (keyFieldValue instanceof Collection) {
					Collection<Object> keyFieldValue_Collection = (Collection<Object>) keyFieldValue;
					//TODO reuse Collection instance
					Collection<Object> fieldValue_Collection = new ArrayList<Object>();
					for (Object keyFieldValue_instance : keyFieldValue_Collection) {
						fieldValue_Collection.add(pm.getObjectById(null, keyFieldValue_instance));
					}
					fieldValue = fieldValue_Collection;
				} else {
					fieldValue = pm.getObjectById(null, keyFieldValue);
				}
				field.set(pc, fieldValue);
			} catch (IllegalArgumentException e) {
				throw new RuntimeException(e);
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			}
		}
	}

	protected javax.jdo.PersistenceManager getPersistenceManager(Object pc) {
		return JDOHelper.getPersistenceManager(pc);
	}

	private static Field getPolymorphicRelationshipKeyField(Field field) {
		Class<?> clazz = field.getDeclaringClass();
		PolymorphicRelationship annotation = field.getAnnotation(PolymorphicRelationship.class);
		if (annotation == null) {
			throw new RuntimeException("Field is not a PolymorphicRelationship");
		}
		Field keyField;
		try {
			keyField = clazz.getField(annotation.keyField());
		} catch (SecurityException e) {
			throw new RuntimeException(e);
		} catch (NoSuchFieldException e) {
			throw new RuntimeException(e);
		}
		// if (field.getType().isAssignableFrom(Collection.class)) {
		// if (!field.getType().equals(keyField.getType())) {
		// throw new RuntimeException("Collection field type mismatch");
		// }
		// }
		return keyField;
	}

	private static Field[] getPolymorphicRelationshipFields(Object pc) {
		Class<?> clazz = pc.getClass();
		Field[] fields = clazz.getFields();
		ArrayList<Field> polymorphicRelationshipFields = new ArrayList<Field>();
		for (Field field : fields) {
			PolymorphicRelationship annotation = field.getAnnotation(PolymorphicRelationship.class);
			if (annotation == null) {
				continue;
			}
			log.info("PolymorphicRelationship: " + field.getName());
			polymorphicRelationshipFields.add(field);
		}
		return polymorphicRelationshipFields.toArray(new Field[polymorphicRelationshipFields.size()]);
	}

}
